﻿#region License
// (c) Intergen.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
#endregion

using System;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Text;
using System.Collections.Generic;

namespace TextGlow.Control.Utilities
{
  public struct RomanTuple
  {
    public int Value;
    public string Digit;

    public RomanTuple(int value, string digit)
    {
      Value = value;
      Digit = digit;
    }
  }

  public enum NumberFormatOptions
  {
    Cardinal,
    Ordinal
  }

  public static class NumberUtils
  {
    private static readonly string[] OrdinalSuffixes = new string[] { "th", "st", "nd", "rd" };
    //private static readonly string[] OrdinalSuffixes = new string[] { "th", "st", "nd", "rd" };


    private static IList<string> OrdinalLargeWords = new List<string>()
    {
      "Billionth", "Millionth", "Thousandth", "Hundredth"
    };

    private static IList<string> OrdinalTwentyNumberWords = new List<string>()
    {
      "First", "Second", "Third", "Fourth", "Fifth", "Sixth", "Seventh", "Eighth", "Ninth", "Tenth", "Eleventh",
      "Twelfth", "Thirteenth", "Fourteenth", "Fifteenth", "Sixteenth", "Seventeenth", "Eighteenth", "Nineteenth"
    };

    public static IList<string> OrdinalHundredNumberWords = new List<string>()
    {
      "Twentieth", "Thirtieth", "Fortieth", "Fiftieth", "Sixtieth", "Seventieth", "Eightieth", "Ninetieth"
    };

    private static IList<string> LargeWords = new List<string>()
    {
      "Billion", "Million", "Thousand", "Hundred"
    };

    private static IList<string> TwentyNumberWords = new List<string>()
    {
      "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven",
      "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"
    };

    public static IList<string> HundredNumberWords = new List<string>()
    {
      "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"
    };


    public static string GetOrdinal(int value)
    {
      int tenth = value % 10;

      if (tenth >= OrdinalSuffixes.Length)
      {
        return OrdinalSuffixes[0];
      }
      else
      {
        // special case for 11, 12, 13
        int hundredth = value % 100;
        if (hundredth >= 11 && hundredth <= 13)
          return OrdinalSuffixes[0];

        return OrdinalSuffixes[tenth];
      }
    }

    public static string ToWord(long value)
    {
      return ToWord(value, NumberFormatOptions.Cardinal);
    }

    public static string ToOrdinalWord(long value)
    {
      return ToWord(value, NumberFormatOptions.Ordinal);
    }

    private static int Billion = 0;
    private static int Million = 1;
    private static int Thousand = 2;
    private static int Hundred = 3;
     
    private static string ToWord(long value, NumberFormatOptions format)
    {
      long currentValue = value;
      StringBuilder sb = new StringBuilder();

      if (currentValue >= 1000000000)
      {
        GetWord(sb, CalculateFormat(format, currentValue % 1000000000), currentValue / 1000000000, Billion);
        currentValue = currentValue % 1000000000;
      }
      if (currentValue >= 1000000)
      {
        GetWord(sb, CalculateFormat(format, currentValue % 1000000), currentValue / 1000000, Million);
        currentValue = currentValue % 1000000;
      }
      if (currentValue >= 1000)
      {
        GetWord(sb, CalculateFormat(format, currentValue % 1000), currentValue / 1000, Thousand);
        currentValue = currentValue % 1000;
      }
      if (currentValue >= 100)
      {
        GetWord(sb, CalculateFormat(format, currentValue % 100), currentValue / 100, Hundred);
        currentValue = currentValue % 100;
      }
      if (currentValue >= 20)
      {
        GetWord(sb, format, currentValue, null);
        currentValue = currentValue % 10;
      }
      else if (currentValue > 0)
      {
        GetWord(sb, format, currentValue, null);
        currentValue = 0;
      }

      return sb.ToString();
    }

    private static NumberFormatOptions CalculateFormat(NumberFormatOptions format, long remainder)
    {
      if (format == NumberFormatOptions.Cardinal)
        return format;
      else if (remainder == 0)
        return NumberFormatOptions.Ordinal;
      else
        return NumberFormatOptions.Cardinal;
    }

    private static void GetWord(StringBuilder sb, NumberFormatOptions format, long value, int? postfix)
    {
      long currentValue = value;

      while (currentValue > 0)
      {
        if (currentValue > 100)
        {
          GetWord(sb, CalculateFormat(format, currentValue % 100), currentValue / 100, Hundred);
          currentValue = currentValue % 100;
        }
        else if (currentValue >= 20)
        {
          sb.DelimitAppend(GetLessThanHundredWord(CalculateFormat(format, currentValue % 10), (int)currentValue / 10));
          currentValue = currentValue % 10;
        }
        else
        {
          sb.DelimitAppend(GetLessThanTwentyWord((postfix == null) ? format : NumberFormatOptions.Cardinal, (int)currentValue));
          currentValue = 0;
        }
      }

      if (postfix != null)
      {
        IList<string> words = (format == NumberFormatOptions.Cardinal) ? LargeWords : OrdinalLargeWords;
        sb.DelimitAppend(words[postfix.Value]);
      }
    }

    private static string GetLessThanTwentyWord(NumberFormatOptions format, int number)
    {
      if (number < 1)
        throw new ArgumentOutOfRangeException("number");
      if (number > 19)
        throw new ArgumentOutOfRangeException("number");

      IList<string> words = (format == NumberFormatOptions.Cardinal) ? TwentyNumberWords : OrdinalTwentyNumberWords;
      return words[number - 1];
    }

    private static string GetLessThanHundredWord(NumberFormatOptions format, int number)
    {
      if (number < 2)
        throw new ArgumentOutOfRangeException("number");
      if (number > 9)
        throw new ArgumentOutOfRangeException("number");

      IList<string> words = (format == NumberFormatOptions.Cardinal) ? HundredNumberWords : OrdinalHundredNumberWords;
      return words[number - 2];
    }

    public static string ToLetter(int value)
    {
      if (value < 1)
        throw new ArgumentOutOfRangeException("value");

      int letterNumber = (value - 1) % 26;
      int letterCount = ((value - 1) / 26) + 1;

      char letter = Convert.ToChar(letterNumber + 65);

      return new string(letter, letterCount);
    }

    public static string ToRomanNumeral(int value)
    {
      //http://blogs.msdn.com/mwade/archive/2007/01/12/adrian-s-revenge.aspx

      StringBuilder roman = new StringBuilder();

      if (value <= 0 || value >= 4000)
        throw new OverflowException("Value was either too large or too small for a Roman Numeral");

      RomanTuple thousand = new RomanTuple(1000, "M");
      RomanTuple fiveHundred = new RomanTuple(500, "D");
      RomanTuple oneHundred = new RomanTuple(100, "C");
      RomanTuple fifty = new RomanTuple(50, "L");
      RomanTuple ten = new RomanTuple(10, "X");
      RomanTuple five = new RomanTuple(5, "V");
      RomanTuple one = new RomanTuple(1, "I");

      while (value >= thousand.Value)
      {
        AddDigitToString(ref value, thousand.Value, thousand.Digit, roman);
      }
      AddDigitRangeToString(ref value, thousand, fiveHundred, oneHundred, roman);
      AddDigitRangeToString(ref value, oneHundred, fifty, ten, roman);
      AddDigitRangeToString(ref value, ten, five, one, roman);

      return roman.ToString();
    }

    private static void AddDigitRangeToString(ref int arabic, RomanTuple high, RomanTuple middle, RomanTuple low, StringBuilder roman)
    {
      AddDigitToString(ref arabic, high.Value - low.Value, low.Digit + high.Digit, roman);
      AddDigitToString(ref arabic, middle.Value, middle.Digit, roman);
      AddDigitToString(ref arabic, middle.Value - low.Value, low.Digit + middle.Digit, roman);
      while (arabic >= low.Value)
      {
        AddDigitToString(ref arabic, low.Value, low.Digit, roman);
      }
    }

    private static void AddDigitToString(ref int arabic, int value, string romanDigit, StringBuilder roman)
    {
      if (arabic >= value)
      {
        roman.Append(romanDigit);
        arabic = arabic - value;
      }
    }

    public static bool GreaterThan(this double value1, double value2)
    {
      return (value1 > value2 && !AreClose(value1, value2));
    }

    public static bool AreClose(double value1, double value2)
    {
      if (value1 == value2)
      {
        return true;
      }
      // why are some wpf values so imprecise?
      double num = ((Math.Abs(value1) + Math.Abs(value2)) + 10.0) * 2.2204460492503131E-6;
      double num2 = value1 - value2;
      return ((-num < num2) && (num > num2));
    }
  }
}